﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using SdmxMl.Common;
using SdmxMl.Helper;
using SdmxMl.Manager;
using SdmxMl.Structure;
using SdmxMl.Msg;

namespace SdmxMl.Structure
{
    /// <summary>
    /// Helper class to generate a Snapshot of an Historized Codelist.
    /// It requires coderef with valid from / to.
    /// If Historized code are used, the OFS code must be located in Annotation CODE_OFS.
    /// </summary>
    public class HclSnapShot
    {
        private static string[] arrTitle =
        {
        "Official List of Swiss Communes - Version {0}",
        "Amtliches Gemeindeverzeichnis der Schweiz - Stand vom {0}",
        "Liste officielle des communes de la Suisse - Etat au {0}",
        "Elenco ufficiale dei Comuni della Svizzera - stato al {0}"
        };
        private static string[] arrDescr =
        {
        "This version of the Official List of Swiss Communes includes any legally binding changes made up to and including {0}." + Environment.NewLine +  Environment.NewLine +

"According to Art. 19 of the Swiss Federal Ordinance of 21st May 2008 on geographical names (GeoNV - SR 510.625) the commune names and numbers as listed in the official commune register must be used by all public authorities. " + Environment.NewLine +  Environment.NewLine +

"The Federal Statistical Office assigns a number to each commune and creates, administers and publishes the official Swiss commune register (GeoNV - Art. 19, para. 1). All changes to commune names approved by the Federal Directorate of Cadastral Surveying (swisstopo) and any other changes notified by the cantonal office concerned (abolition of communes, changes to commune boundaries and other changes in the districts or comparable administrative entities of the canton) are to be noted in the official register. " + Environment.NewLine +  Environment.NewLine +

"The official commune register is structured according to cantons and districts or comparable administrative entities. (GeoNV - Art. 19, para. 2). Various federal, cantonal and communal governments and private businesses use this register when identifying and referring to communes. " + Environment.NewLine +  Environment.NewLine +

"1 January 1960 (SFSO-assigned codes for communes)",

        "Dieser Stand enthält die Liste aller Gemeinden, inklusive der bis und mit {0} rechtskräftig gewordenen Änderungen." + Environment.NewLine +  Environment.NewLine +

"Nach Art. 19, Abs. 3 der Verordnung vom 21. Mai 2008 über die geographischen Namen (GeoNV - SR 510.625) sind die Gemeindenamen und Gemeindenummern des amtlichen Gemeindeverzeichnisses behördenverbindlich. " + Environment.NewLine +  Environment.NewLine +

"Das Bundesamt für Statistik vergibt für jede Gemeinde eine Nummer und erstellt, verwaltet und veröffentlicht das amtliche Gemeindeverzeichnis der Schweiz (GeoNV - Art. 19, Abs. 1). Im amtlichen Gemeindeverzeichnis werden alle von der Eidgenössischen Vermessungsdirektion (swisstopo) genehmigten Änderungen von Gemeindenamen sowie weitere von den zuständigen kantonalen Stellen gemeldeten Änderungen (Aufhebung von Gemeinden, Gebietsveränderungen und Änderungen in den Bezirken oder einer vergleichbaren administrativen Einheit des Kantons) nachgeführt. " + Environment.NewLine +  Environment.NewLine +

"Das amtliche Gemeindeverzeichnis ist nach Kantonen sowie nach Bezirken oder einer vergleichbaren administrativen Einheit des Kantons gegliedert (GeoNV - Art. 19, Abs. 2). Es wird als definitorische Grundlage zu Gemeindeidentifikation und Gemeindenamen in zahlreichen Verwaltungsapplikationen auf Stufe Bund, Kantone und Gemeinden sowie in der Privatwirtschaft eingesetzt. " + Environment.NewLine +  Environment.NewLine +

"01.01.1960 (BFS-Gemeindenummer)"
,

        "Cet état contient la liste de toutes les communes ainsi que les changements entrés en vigueur jusqu'au {0} inclus." + Environment.NewLine +  Environment.NewLine +

"En vertu de l’art. 19, al. 3 de l’ordonnance du 21 mai 2008 sur les noms géographiques (ONGéo; RS 510.625), les noms et les numéros de communes indiqués dans le répertoire officiel des communes de Suisse ont force obligatoire pour les autorités." + Environment.NewLine +  Environment.NewLine +

"L’Office fédéral de la statistique attribue un numéro à chaque commune et établit, gère et publie le répertoire officiel des communes de Suisse (art. 19, al. 1, ONGéo). Celui-ci dresse la liste des modifications de noms de communes approuvées par la Direction des mensurations cadastrales (swisstopo) et des modifications annoncées par les services cantonaux compétents (suppression de communes, modifications de limites territoriales et modifications concernant des districts ou toute autre entité administrative comparable d’un canton). " + Environment.NewLine +  Environment.NewLine +

"Le répertoire officiel des communes de Suisse est subdivisé par canton et par district ou toute autre entité administrative comparable du canton (art. 19, al. 2 ONGéo). Elle sert de référence pour l'identification et la désignation des communes dans de nombreuses opérations administratives aux niveaux fédéral, cantonal et communal de même que dans le secteur privé." + Environment.NewLine +  Environment.NewLine +

"01.01.1960 (Numéro OFS de la commune)",

        "Questa versione comprende l'elenco di tutti i Comuni incluse le mutazioni entrate in vigore al {0}." + Environment.NewLine +  Environment.NewLine +

"Conformemente all’articolo 19, capoverso 3 dell’ordinanza del 21 maggio 2008 sui nomi geografici (ONGeo - RS 510.625), i nomi e i numeri dell’elenco ufficiale dei Comuni sono vincolanti per le autorità." + Environment.NewLine +  Environment.NewLine +

"L’Ufficio federale di statistica attribuisce a ogni Comune un numero vincolante e allestisce, gestisce e pubblica l’elenco ufficiale dei Comuni della Svizzera (ONGeo - art. 19, cpv. 1). Nell’elenco ufficiale dei Comuni sono inserite e aggiornate tutte le modifiche di nomi di Comuni approvate dalla Direzione federale delle misurazioni catastali (swisstopo) e tutte le altre mutazioni (eliminazioni di Comuni, cambiamenti territoriali, distrettuali o di altre unità amministrative comparabili) comunicate dai servizi cantonali competenti." + Environment.NewLine +  Environment.NewLine +

"L’Elenco ufficiale dei Comuni della Svizzera è suddiviso per Cantoni e distretti o unità amministrative cantonali comparabili (ONGeo, art. 19, cpv. 2) ed è utilizzato quale strumento definitorio per l’identificazione e la denominazione dei Comuni in varie applicazioni amministrative da parte della Confederazione, dei Cantoni e dei Comuni nonché dall’economia privata." + Environment.NewLine +  Environment.NewLine +

"01.01.1960 (Numeri UST dei Comuni)"

        };
        

        #region Internal members

        private ArtefactManager _armHierarchy;
        private ArtefactManager _armCodelist;
        private HierarchicalCodelistType _historizedHierarchy;
        private Dictionary<string, CodeListType> _dicoCodeList;
        private string _dateFormatPattern = "yyyyMMdd";
        private string _defaultValidFrom;

        #endregion //Internal members

        #region Properties

        public ArtefactManager ArmHierarchy
        {
            get { return _armHierarchy; }
        }

        public ArtefactManager ArmCodelist
        {
            get { return _armCodelist; }
        }


        #endregion

        #region Construction

        /// <summary>Parametrized C'tor </summary>
        /// <param name="hierarchyArtefact">The artefact holding the historized hierarchy</param>
        public HclSnapShot(HierarchicalCodelistType hierarchyArtefact)
        {
            
            //pre conditions: The hierarchy must exist.
            _historizedHierarchy = hierarchyArtefact;
            if (_historizedHierarchy == null || 
                _historizedHierarchy.HierarchyList.Count == 0 || 
                _historizedHierarchy.CodelistRefList.Count == 0)
                throw new ApplicationException("Invalid HierarchicalCodelist artefact given as Snapshot Parameter !");

            _dicoCodeList = _historizedHierarchy.CodelistRefList.GetRefDictionary();
            // The refered codelists must be accessible in memory.
            if (_dicoCodeList.Count != _historizedHierarchy.CodelistRefList.Count)
                throw new ApplicationException("Refered Codelists not accessible in memory!");

            _defaultValidFrom = _historizedHierarchy.ValidFrom;
        }

        #endregion //Construction

        #region Public Methods

        /// <summary>Creates the snapshot at specified date</summary>
        /// <param name="useHistoryCode"></param>
        /// <param name="makeHierarchy"></param>
        /// <param name="SnapShotTime">Starting date for SnapShot</param>
        public void Generate(bool useHistoryCode, bool makeHierarchy, DateTime SnapShotTime, string subVersion)
        {
            _armHierarchy = null;
            // This artefact container will be used in all cases
            _armCodelist = new ArtefactManager(SdmxArtefactType.CodeLists);

            if (makeHierarchy)
                _armHierarchy = GenerateHierarchy(_armCodelist, SnapShotTime, useHistoryCode, subVersion);
            else
                GenerateOneCodelist(_armCodelist, SnapShotTime, useHistoryCode);

        }

        #endregion

        #region Hierarchy Snapshot
        /// <summary>Remove conventional _HIST of historized artegact in id</summary>
        /// <param name="id"></param>/ <returns>Non historized id</returns>
        private string IdWithoutHist(string id)
        {
            string h = "_HIST";

            if (id.Length > h.Length)
            {
                int i = id.IndexOf(h);
                if (i > 0 && i == id.Length - h.Length)
                    return id.Substring(0, i);
            }
            //PRA-PY
           // id = id.Replace("H", "");
            return id;
        }

        private void DatedName(MultiLang ml, DateTime snapshotDate)
        {
            if (ml != null)
            {
                foreach (string lg in LangSupport.SupportedLanguages)
                {
                    ml.UpdateForLanguage(lg, ml.GetIfDefinedForLanguage(lg) + " (" +
                        snapshotDate.ToString("dd.MM.yyyy") + ")");
                }
            }
        }

        private ArtefactManager GenerateHierarchy(ArtefactManager arm, DateTime snapshotDate, bool useHistoryCode, string subVersion)
        {
            int i = 0;
            ArtefactManager armHierarchy = new ArtefactManager(SdmxArtefactType.HierarchicalCodelists);
            HierarchicalCodelistType he = new HierarchicalCodelistType();
            armHierarchy.ArtefactList.Add(he);
            he.Id = IdWithoutHist(_historizedHierarchy.Id);
            he.AgencyId = _historizedHierarchy.AgencyId;

            if (_historizedHierarchy.Id != "HCL_HGDE_HIST")
            {
                he.Name = _historizedHierarchy.Name.Clone();
                DatedName(he.Name, snapshotDate);
            }
            else
            {
                i = 0;
                foreach (string lg in LangSupport.SupportedLanguages)
                    he.Name.UpdateForLanguage(lg, string.Format(arrTitle[i++], snapshotDate.ToString("dd.MM.yyyy")));
                i = 0;
                foreach (string lg in LangSupport.SupportedLanguages)
                    he.Description.UpdateForLanguage(lg, string.Format(arrDescr[i++], snapshotDate.ToString("dd.MM.yyyy")));
            }

            he.Version = snapshotDate.Year.ToString() + "." + subVersion;
            he.ValidFrom = snapshotDate.ToString("yyyy-MM-dd");

            HierarchyType heType = new HierarchyType(he.CodelistRefList);
            heType.Id = IdWithoutHist(_historizedHierarchy.HierarchyList[0].Id);

            //PRA-PY heType.Version = snapshotDate.ToString(_dateFormatPattern);
            if (_historizedHierarchy.Id != "HCL_HGDE_HIST")
            {
                heType.Name = _historizedHierarchy.Name.Clone();
                DatedName(heType.Name, snapshotDate);
            }
            else
            {
                i = 0;
                foreach (string lg in LangSupport.SupportedLanguages)
                    heType.Name.UpdateForLanguage(lg, string.Format(arrTitle[i++], snapshotDate.ToString("dd.MM.yyyy")));
            }

            heType.LevelList.AddRange(_historizedHierarchy.HierarchyList[0].LevelList.Clone());
            he.HierarchyList.Clear();
            he.HierarchyList.Add(heType);

            foreach (CodelistRefType clrefType in _historizedHierarchy.CodelistRefList)
            {
                // Create references
                CodelistRefType che = new CodelistRefType();
                che.AgencyId = clrefType.AgencyId;
                che.Id = clrefType.Id;
                che.Alias = clrefType.Alias;
                che.Version = snapshotDate.Year.ToString() + "." + subVersion;
                he.CodelistRefList.Add(che);

                // Create One codelist per level
                CodeListType clType = _dicoCodeList[clrefType.Alias];

                CodeListType clt = arm.CreateArtefact() as CodeListType;
                arm.ArtefactList.Add(clt);
                clt.Id = clrefType.Id;
                clt.AgencyId = clrefType.AgencyId;
                clt.Version = snapshotDate.Year.ToString() + "." + subVersion;
                clt.Name = clType.Name.Clone();
                DatedName(clt.Name, snapshotDate);
                clt.CodeList.HasHierarchy = false;

            }

            // Recurse
            FillHierarchyCodesAt(snapshotDate, _historizedHierarchy.HierarchyList[0].CodeRefList, he.HierarchyList[0].CodeRefList, arm, 0, useHistoryCode == false);

            //Sort if needed
            if (useHistoryCode == false)
                foreach (CodeListType clt in arm.ArtefactList)
                    clt.CodeList.Sort(new ItemCompareNum());

            // Order references also
            OrderHierarchyCodesAt(he.HierarchyList[0].CodeRefList);

            return armHierarchy;
        }

        private void FillHierarchyCodesAt(DateTime snapshotDate, CodeRefTypeList orgList, CodeRefTypeList heList, ArtefactManager arm, int level, bool ofsCode)
        {
            // Nothing more ?
            if (orgList.Count <= 0)
                return;

            CodeListType clt = arm.ArtefactList[level] as CodeListType;
            foreach (CodeRefType crt in orgList)
            {
                if (AddThisCode(crt, snapshotDate))
                {
                    // now fetch code info
                    CodeType code = FetchCode(crt, ofsCode, snapshotDate);
                    clt.CodeList.Add(code);

                    // Crete associated reference item in hierarchy
                    CodeRefType heRefcode = new CodeRefType();
                    heRefcode.CodeID = code.Id;
                    heRefcode.CodelistAliasRef = crt.CodelistAliasRef;
                    heRefcode.ValidFrom = crt.ValidFrom;
                    heRefcode.ValidTo = crt.ValidTo;
                    heList.Add(heRefcode);

                    // Recurse
                    FillHierarchyCodesAt(snapshotDate, crt.CodeRefList, heRefcode.CodeRefList, arm, level + 1, ofsCode);
                }               
            }
        }

        private void OrderHierarchyCodesAt(CodeRefTypeList heList)
        {
            if (heList.Count > 1)
                heList.Sort(new CodeRefCompareNum());
            foreach (CodeRefType crt in heList)
                OrderHierarchyCodesAt(crt.CodeRefList);
        }
 
        #endregion // Hierarchy Snapshot

        #region Codelist SnapShot

        private void GenerateOneCodelist(ArtefactManager arm, DateTime snapshotDate, bool useHistoryCode)
        {
            Artefact a = arm.CreateArtefact();
            arm.ArtefactList.Add(a);
            CodeListType clt = a as CodeListType;

            int ndx = _historizedHierarchy.Id.IndexOf("HCL_");
            string anId = ndx == 0 ? _historizedHierarchy.Id.Substring(ndx) : _historizedHierarchy.Id;
            clt.Id = "CL_" + anId;
            clt.AgencyId = _historizedHierarchy.AgencyId;

            clt.Version = snapshotDate.ToString(_dateFormatPattern);
            clt.Name = _historizedHierarchy.Name.Clone();
            clt.CodeList.HasHierarchy = true;

            // Recurse
            FillCodesAt(snapshotDate, _historizedHierarchy.HierarchyList[0].CodeRefList, clt, string.Empty, useHistoryCode == false);
        }


        public void FillCodesAt(DateTime snapshotDate, CodeRefTypeList list, CodeListType clt, string parentCodeId, bool ofsCode)
        {
            foreach (CodeRefType crt in list)
            {
                if (AddThisCode(crt, snapshotDate))
                {
                    // now fetch code info
                    CodeType code = FetchCode(crt, ofsCode, snapshotDate);
                    code.Parent = parentCodeId;
                    clt.CodeList.Add(code);

                    // Recurse
                    FillCodesAt(snapshotDate, crt.CodeRefList, clt, code.Id, ofsCode);
                }
            }
        }
        #endregion //Codelist SnapShot

        #region Snapshot Helpers

        /// <summary>Determines if hierarchy coderef is candicatre for snapshot code</summary>
        /// <param name="crt">the current coderef to analyse</param>
        /// <param name="snapshotDate">the date of snapshot</param>
        /// <returns>true if has to be part of snapshot</returns>
        private bool AddThisCode(CodeRefType crt, DateTime snapshotDate)
        {
            bool addCode = false;
            DateTime dtFrom = DateTime.MaxValue;

            if (string.IsNullOrEmpty(crt.ValidFrom) == false)
                dtFrom = Convert.ToDateTime(crt.ValidFrom);
            else if (string.IsNullOrEmpty(_defaultValidFrom) == false)
                dtFrom = Convert.ToDateTime(_defaultValidFrom);

            addCode = snapshotDate >= dtFrom;

            if (string.IsNullOrEmpty(crt.ValidTo) == false && addCode)
            {
                DateTime dtTo = Convert.ToDateTime(crt.ValidTo);
                addCode = snapshotDate <= dtTo;
            }

            // Special case validfrom same as validto, we must ignore
            if (addCode && crt.ValidFrom.Length > 0)
                if (crt.ValidFrom == crt.ValidTo)
                    addCode = false;

            return addCode;
        }

       /// <summary>From reference, clone the code and adapts it's attributes to snapshot options</summary>
       /// <param name="crt">reference to code</param> 
       /// <param name="ofsCode">if true use code instead of history code</param>
       /// <param name="snapshotDate"></param>
       /// <returns>a new instance of code</returns>
        private CodeType FetchCode(CodeRefType crt, bool ofsCode, DateTime snapshotDate)
        {
            CodeType code = new CodeType();
            CodeListType clType = _dicoCodeList[crt.CodelistAliasRef];

            // Get refered code
            ISdmxHierarchyListItem item = clType.CodeList.LocateItem(crt.CodeID);
            if (item == null)
                throw new ApplicationException("Refered code not found in codelist.");

            // Clone and adapt content
            string codeId = item.Id;
        // PRA-PY    code.AnnotationList = item.AnnotationList.Clone();

            // If an annotation of type "CODE_OFS" exists replace iten code (history) by the text of the annotation
            if (ofsCode)
            {
                string ofs = item.AnnotationList.GetTextOfAnnotationType("CODE_OFS");
                if (string.IsNullOrEmpty(ofs) == false)
                {
                    codeId = ofs;
                    // And create annotation for OFS_History number
                    Annotation ann = new Annotation("CODE_HISTORY");
                    ann.Text = new MultiLang(item.Id);
                    code.AnnotationList.Add(ann);
                }

                //PRA-PY code.AnnotationList.RemoveAnnotationType("CODE_OFS");

            }

            //PRA-PY Provides annotation of type ABBREV
            Annotation annAbbrev = item.AnnotationList.GetAnnotationType("ABBREV");
            if (annAbbrev != null)
                code.AnnotationList.Add(annAbbrev);


            code.Id = codeId;
            code.Name = item.Name.Clone();

            // Some code have the description that slightly evolve through time
            // if it is the case retrieve description according to snapshot period
            DateTime olderDate = DateTime.MaxValue;
            foreach (Annotation ann in code.AnnotationList)
            {
                if (ann.Type == "OFS_RENAME")
                {
                    DateTime datePrevText = Convert.ToDateTime(ann.Title);
                    if (snapshotDate < datePrevText && datePrevText < olderDate)
                    {
                        code.Name = ann.Text.Clone();
                        olderDate = datePrevText;
                    }
                }
            }
            code.AnnotationList.RemoveAnnotationType("OFS_RENAME");

            return code;
        }

        #endregion //Snapshot Helpers
    }
}
